from inspect import signature, _empty
from optparse import Option
from crispy_forms.utils import render_crispy_form
from django.contrib.auth.forms import get_user_model
from django.contrib.auth.mixins import AccessMixin, PermissionRequiredMixin
from django.http import JsonResponse
from django.views import View
from django.views.generic.detail import SingleObjectMixin, SingleObjectTemplateResponseMixin
from django.views.generic.edit import ContextMixin, CreateView, FormMixin, ModelFormMixin, ProcessFormView
from django.core.exceptions import ImproperlyConfigured
from typing import Optional
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
from django.template.context_processors import csrf
from crispy_forms.layout import Submit
from audit.models import AuditManagement
from audit.forms import create_audit_form

class DetailView(SingleObjectTemplateResponseMixin, SingleObjectMixin, View):
    """
    Modified DetailView to avoid duplicate `get_object` queries.
    """
    def get(self, request, *args, **kwargs):
        if not getattr(self, "object", None):
            self.object = self.get_object()
        context = self.get_context_data(object=self.object)
        return self.render_to_response(context)


class UpdateView(SingleObjectTemplateResponseMixin, ModelFormMixin, ProcessFormView):
    """
    Modified UpdateView to avoid duplicate `get_object` queries.
    """
    template_name_suffix = "_form"

    def get(self, request, *args, **kwargs):
        if not getattr(self, "object", None):
            self.object = self.get_object()
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        if not getattr(self, "object", None):
            self.object = self.get_object()
        return super().post(request, *args, **kwargs)
    


# NOTE for `update` form, use Django builtin `CreateView` 
class ReadOnlyFormMixin(ContextMixin):
    """
    NOTE Use it with DetailView or views that provide 'object' in context.
    Note that MRO order.
    """
    form_class: Optional[type] = None

    def get_context_data(self, **kwargs):
        if not self.form_class:
            raise ImproperlyConfigured(
                "%(cls)s misses `form_class` field." % {"cls": self.__class__.__name__}
            )
        context = super().get_context_data(**kwargs)
        object = context['object']
        form = self.form_class(instance=object)
        for field in form.fields.values():
            field.disabled = True
        context["form"] = form
        return context


class PermissionCheckMethodMixin(AccessMixin):
    """
    By default, it uses `get_object` method to get target object, which is defined
    in SingleObjectMixin. You can override `get_check_object` method to get target
    object, or place this Mixin after views like DetailView
    """
    perm_check_func_name = None

    def get_check_object(self):
        if not getattr(self, "object", None):
            if func := getattr(self, "get_object"):
                self.object = func(self)
            else:
                return None
        return self.object
    
    def dispatch(self, request, *args, **kwargs):
        user = self.request.user
        object = self.get_check_object()
        if self.perm_check_func_name is None:
            raise ImproperlyConfigured(
                f"Please set `perm_check_func_name` to be a Non-None value!"
            )
        if func := getattr(object, self.perm_check_func_name, None):
            if callable(func):
                sig = signature(func)
                if len(sig.parameters) == 1 and sig.parameters.get("user"):
                    if func(user):
                        return super().dispatch(request, *args, **kwargs)
                    else:
                        return self.handle_no_permission()
                else:
                    raise ImproperlyConfigured(
                        f"The parameters signature of the function `{self.perm_check_func_name}` "
                        "doesn't match (user: UserProfile, )!"
                    )
            else:
                raise ImproperlyConfigured(
                    f"`{self.perm_check_func_name}` is not a function!"
                )
        else:
            raise ImproperlyConfigured(
                f"Cannot find function `{self.perm_check_func_name}`!"
            )


class CrispyFormMixin(FormMixin):
    """
    The meaning for this mixin is to provide a different action url for form without
    modifying template file.
    """
    form_action_view_name = ""
    submit_button_name = _("提交")
    
    def get_form_action_view_name(self):
        if not self.form_action_view_name:
            raise ImproperlyConfigured(
                f"{self.__class__.__name__} misses `form_action_view_name` field."
            )
        return reverse(self.form_action_view_name)
    
    def get_form(self, *args, **kwargs):
        """
        Note this works in {% cripsy form %}, but not {{ form|crispy }}
        """
        form = super().get_form(*args, **kwargs)
        form.helper.form_action = self.get_form_action_view_name()
        submit_btn = Submit('submit', self.submit_button_name)
        submit_btn.field_classes = 'tw:btn tw:btn-neutral'
        form.helper.add_input(submit_btn)
        return form


class HTMXFormMixin(SingleObjectTemplateResponseMixin, CrispyFormMixin):
    """
    template_name is required to render a normal view, which will be used as
    form error view as view.
    
    form_action_view_name:
      the view name for this view itself. When used as HTMX component, it requires
      we to specify a form_action

    Remember to implement `get_success_url`, which is used after a success validation.
    """
    template_name = "form_base.html"
    htmx_template_name = "form.html"
    
    def get_template_names(self): # pyright:ignore [ reportIncompatibleMethodOverride ]
        if self.request.htmx:
            return [self.htmx_template_name]
        return super().get_template_names()
    

class HTMXCreateFormView(HTMXFormMixin, CreateView): # pyright:ignore [ reportIncompatibleMethodOverride ]
    pass

class HTMXUpdateFormView(HTMXFormMixin, UpdateView): # pyright:ignore [ reportIncompatibleMethodOverride ]
    pass

class AuditFormPostAPIView(
    UpdateView,
):
    """
    You should define `model` class attribute in your derived class, which is
    the model of the object which we are auditing
    """
    def get_object(self, queryset=None):
        pk = self.kwargs.get(self.pk_url_kwarg)
        obj = self.model.objects.get(pk=pk)
        return AuditManagement.objects.get_for_object(obj)

    def get_form_class(self):
        return create_audit_form()

    def form_valid(self, form): # pyright:ignore [ reportIncompatibleMethodOverride ]
        user = self.request.user
        obj = form.save(commit=False)
        obj.auditor = user
        obj.save()
        form.save_m2m()
        self.object = obj
        return JsonResponse({
            'success': True
        })

    def form_invalid(self, form):
        ctx = {}
        ctx.update(csrf(self.request))
        form_html = render_crispy_form(form, context=ctx)
        return JsonResponse({
            'success': False,
            'form_html': form_html
        })
    

    
class LoginRequiredJsonResponseMixin(AccessMixin):
    """Verify that the current user is authenticated."""

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return JsonResponse({
                'error': 'Authentication required'
            }, status=401)
        return super().dispatch(request, *args, **kwargs)
